/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package org.sunspotworld.sensortasks.routingtasks;

import com.sun.spot.peripheral.ChannelBusyException;
import com.sun.spot.peripheral.NoAckException;
import com.sun.spot.peripheral.Spot;
import com.sun.spot.peripheral.radio.IRadiogramProtocolManager;
import com.sun.spot.peripheral.radio.LowPan;
import com.sun.spot.peripheral.radio.RadioPacket;
import com.sun.spot.peripheral.radio.RadioPolicy;
import com.sun.spot.util.IEEEAddress;
import com.sun.spot.util.Utils;
import java.util.Hashtable;
import java.util.Random;
import org.sunspotworld.market.Bid;
import org.sunspotworld.market.MarketAgent;
import org.sunspotworld.protocols.BidProtocol;
import org.sunspotworld.protocols.MyProtocols;
import org.sunspotworld.sensortasks.SensorTask;
import org.sunspotworld.sensortasks.TimeKeeper;

/**
 *
 * @author me
 */
public class AdvertiseTask extends SensorTask{
    private boolean reservation = false;
    private static boolean overheard = false;
    private static final int PAYLOAD_SIZE = RadioPacket.MIN_PAYLOAD_LENGTH - IRadiogramProtocolManager.DATA_OFFSET;
    
    private static Random random = new Random(new IEEEAddress(System.getProperty("IEEE_ADDRESS")).asLong());
    private static final int RANDOM_BACKOFF = 10;
    private static final int CONSTANT_BACKOFF = 2;
    private static final int BROADCAST_RETRIES = 5;
    private long stop = 0;
    private static byte[] advertisement;
    private static int size;
    private Hashtable bids = new Hashtable();
    private double bestBid = 0;
    private int retries = 0;
    private long address;
    private DataRoutingTask owner;
    private int scheduledSlot;
    
    public static void setOverheard() {
        overheard = true;
    }
    
    public AdvertiseTask(long sample, DataRoutingTask task, int slot) {
        super.sampleInterval = sample;
        owner = task;
        scheduledSlot = slot;
    }
    
    private AdvertiseTask(long sample, long id, DataRoutingTask task, int slot) {
        super.sampleInterval = sample;
        super.id = id;
        owner = task;
        scheduledSlot = slot;
    }
    
    protected SensorTask copy() {
        SensorTask task = (SensorTask)new AdvertiseTask(super.sampleInterval, super.id, owner, scheduledSlot);
        task.setRepeat(super.repeating);
        return task;
    }
    
    public void addBid(long address, double bid) {
        bids.put(new Double(bid), new IEEEAddress(address));
        bestBid = Math.max(bestBid, bid);
    }
    
    private void resetBids() {
        bestBid = 0;
        bids = new Hashtable();
    }
    
    public void runTask() {
        stop = TimeKeeper.getInstance().currentTimeMillis()+super.sampleInterval;
        Spot.getInstance().getRadioPolicyManager().policyHasChanged(TimeKeeper.getInstance(), RadioPolicy.ON);
        
        
        //check if supply available
        
        
        if (!reservation) {
            overheard = false;
            BidProtocol.setEnabled(true);
            resetBids();
            //random backoff
            Utils.sleep((random.nextLong()%(super.sampleInterval/4)));
            //send advertisement
            broadcastAdvertisement();
            //waitForResponse
            Utils.sleep((stop - TimeKeeper.getInstance().currentTimeMillis())/2);
            //sendAward
            if (!bids.isEmpty()) {
                address = ((IEEEAddress)bids.get(new Double(bestBid))).asLong();
                sendAward();
            }
            BidProtocol.setEnabled(false);
        } else {
            sendTime();
            Utils.sleep(stop - TimeKeeper.getInstance().currentTimeMillis()); // if reservation, wait for data
        }
        Spot.getInstance().getRadioPolicyManager().policyHasChanged(TimeKeeper.getInstance(), RadioPolicy.OFF);
    }
    
    private void broadcastAdvertisement() {
        if (overheard) {
            super.repeating=false;
            return;
        }
        try {
            updateAdvertisement();
            LowPan.getInstance().sendBroadcast(MyProtocols.ADVERTISEMENT, advertisement, 0, size, 0);
        } catch (ChannelBusyException ex) {
            if (TimeKeeper.getInstance().currentTimeMillis() + RANDOM_BACKOFF + CONSTANT_BACKOFF< stop) {  //if there's enough time left
                //retry if channel busy
                Utils.sleep(random.nextInt(RANDOM_BACKOFF) + CONSTANT_BACKOFF);  // wait a random amount before retrying
                broadcastAdvertisement();
            }
        }
    }
    
    private void updateAdvertisement() {
        //update advertisement information
        advertisement = longToByteArray(TimeKeeper.getInstance().currentTimeMillis());
        size = 8;
    }
    
    private void sendAward () {
        try {
            LowPan.getInstance().sendWithoutMeshingOrFragmentation(MyProtocols.AWARD, address, advertisement, 0, size);
            System.out.println("award sent");
            reservation = true;
            MarketAgent.getInstance().addBid(new Bid(this, PAYLOAD_SIZE, PAYLOAD_SIZE));
        } catch (NoAckException ex) {
            if (retries < BROADCAST_RETRIES && TimeKeeper.getInstance().currentTimeMillis() < stop) {  //if retried less than 5 times and there's enough time left
                retries++;
                sendAward();
            } else {
                ex.printStackTrace();
            }
        } catch (ChannelBusyException ex) {
            if (TimeKeeper.getInstance().currentTimeMillis() + RANDOM_BACKOFF + CONSTANT_BACKOFF < stop) {  //if there's enough time left
                //retry if channel busy
                Utils.sleep(random.nextInt(RANDOM_BACKOFF) + CONSTANT_BACKOFF);  // wait a random amount before retrying
                sendAward();
            }
        }
        retries = 0;  //retries resets after first recursive call of sendPacket falls through to here
    }
    
    private void sendTime () {
        try {
            updateAdvertisement();
            LowPan.getInstance().sendWithoutMeshingOrFragmentation(MyProtocols.TIME, address, advertisement, 0, size);
            System.out.println("time sent");
        } catch (NoAckException ex) {
            if (retries < BROADCAST_RETRIES && TimeKeeper.getInstance().currentTimeMillis() < stop) {  //if retried less than 5 times and there's enough time left
                retries++;
                sendTime();
            } else {
                ex.printStackTrace();
            }
        } catch (ChannelBusyException ex) {
            if (TimeKeeper.getInstance().currentTimeMillis() + RANDOM_BACKOFF + CONSTANT_BACKOFF < stop) {  //if there's enough time left
                //retry if channel busy
                Utils.sleep(random.nextInt(RANDOM_BACKOFF) + CONSTANT_BACKOFF);  // wait a random amount before retrying
                sendTime();
            }
        }
        retries = 0;  //retries resets after first recursive call of sendPacket falls through to here
    }
    
    private byte[] longToByteArray(long value) {
        return new byte[] {
                (byte)(value >>> 56),
                (byte)(value >>> 48),
                (byte)(value >>> 40),
                (byte)(value >>> 32),
                (byte)(value >>> 24),
                (byte)(value >>> 16),
                (byte)(value >>> 8),
                (byte)value};
    }
}
